<?php

class QueryOnDisconnecteDBException extends Exception {
  public function __construct($message = "", $code = 0, $previous = null) {
    parent::__construct($message, $code, $previous);
  }
}

class NoQueryResultsForIteration extends Exception {
  public function __construct($message = "", $code = 0, $previous = null) {
    parent::__construct($message, $code, $previous);
  }
}

class AlreadyConnectedToDBException extends Exception {
  public function __construct($message = "", $code = 0, $previous = null) {
    parent::__construct($message, $code, $previous);
  }
}

class DBError extends Exception {
  public $errors;
  
  public function __construct($errors) {
    parent::__construct();
    $this->errors = $errors;
  }
}

class DBUtils {
  
  const IP = '200.235.131.185';
  const USER_NAME = 'inf220_L02';
  const PASSWORD = '09iuy';
  
  private $connection;
  private $queryResults;  
  private $transacting;
  
  public function hasQueryResults() {
    return gettype($this->queryResults) === "resource";
  }
  
  public function hasConnection() {
    return gettype($this->connection) === "resource";
  }
  
  public function __construct() {
    $this->connection = false;
    $this->queryResults = false;
    $this->transacting = false;
  }
  
  public function connect() {
    if($this->hasConnection()) {
      throw new AlreadyConnectedToDBException();
    }
    else {
      $connectionInfo = array(
          "UID" => DBUtils::USER_NAME,
          "PWD" => DBUtils::PASSWORD,
          'MultipleActiveResultSets' => false
      );
      $this->connection = sqlsrv_connect(DBUtils::IP, $connectionInfo);
      if($this->connection === false) {
        throw new DBError(sqlsrv_errors());
      }
    }
  }
  
  /**
   * Roda uma query no banco de dados. Tanto de consulta quanto de modificação.
   * 
   * @param type $query A query em si, contendo possíveis placeholders.
   * @param type $args Valores para os placeholders.
   */
  public function runQuery($query, $args = null) {
    if($this->hasQueryResults()) {
      sqlsrv_free_stmt($this->queryResults);
      $this->queryResults = false;
    }
    if(!$this->hasConnection()) {
      throw new QueryOnDisconnecteDBException();
    }
    else {
      $this->queryResults = sqlsrv_query($this->connection, $query, $args);
      if(!$this->hasQueryResults()) {
        $this->rollback();
        throw new DBError(sqlsrv_errors());
      }
    }
  }
  
  public function beginTransaction() {
    if(!$this->transacting) {
      $this->runQuery("begin transaction");
      $this->transacting = true;
    }
  }
  
  public function commit() {
    if($this->transacting) {
      $this->runQuery("commit");
      $this->transacting = false;
    }
  }
  
  public function rollback() {
    if($this->transacting) {
      $this->runQuery("rollback");
      $this->transacting = false;
    }
  }
  
  public function close($commit = true) {
    if($this->hasConnection()) {
      if($commit === false) {
        $this->rollback();
      }
      else {
        $this->commit();
      }
      if($this->hasQueryResults()) {
        sqlsrv_free_stmt($this->queryResults);
        $this->queryResults = false;
      }
      sqlsrv_close($this->connection);
      $this->connection = false;
    }
  }
  
  /**
   * Pega o próximo item do resultado.
   * 
   * @return mixed Object ou null caso haja um resultado ou não.
   * @throws NoQueryResultsForIteration Quando não há um resultado de consulta configurado.
   */
  public function getItem() {
    if($this->hasQueryResults()) {
      return sqlsrv_fetch_object($this->queryResults);
    }
    else {
      throw new NoQueryResultsForIteration();
    }
  }
  
}